using System;
using Nemerle.Collections;
using System.Reflection;

namespace NPhilosopher {
	public module CILCompileIA32 {
		public Compile(assembly : NAssembly) : void {
			System.Console.Write(CompileAssembly(assembly));
		}
		
		public CompileAssembly(assembly : NAssembly) : string {
			| Assembly(_, types) =>
				"\tcall method_Main\n" + 
				CompileList(types)
			
			| Type(_, fields, members) =>
				CompileList(fields) + 
				CompileList(members)
			
			| Field(name) =>
				String.Format("\tfield_{0} dd 0\n", name)
			
			| Method(name, locals, args, ret, blocks) when blocks != null =>
				def args = args.Length :> long;
				mutable asm = String.Format("method_{0}:\n", name) + 
				"\t.begin:\n" + 
				"\t\tpush ebp\n" + 
				"\t\tmov ebp, esp\n";
				asm += {
					if(locals == 0)
						""
					else
						String.Format("\t\tsub esp, {0}\n", locals * 4)
				};
				asm += "\t\tjmp .block_0\n"; 
				foreach((key, _) in blocks.KeyValuePairs)
					asm += CompileBlock(blocks, key, args);
				asm + 
				"\t.end:\n" + 
				({
					if(ret.IsAssignableFrom(typeof(void)))
						""
					else
						"\t\tpop eax\n"
				}) + 
				"\t\tmov esp, ebp\n" + 
				"\t\tpop ebp\n" + 
				String.Format(
					"\t\tret {0}\n",
					args * 4
				)
			
			| _ => "";
		}
		
		CompileList(obj : list [NAssembly]) : string {
			| [] => "";
			| head :: tail =>
				CompileAssembly(head) + 
				CompileList(tail)
		}
		
		CompileBlock(blocks : Hashtable [long, NIL.Block], start : long, args : long) : string {
			match(blocks[start]) {
				| Block(pos, insts) =>
					mutable asm = String.Format("\t.block_{0:X}:\n", pos);
					foreach(inst :> NIL.Inst in insts)
						asm += CompileInst(inst, CILToNIT.ConvertInst(inst), args);
					asm;
			}
		}
		
		CompileMath(oper : string) : string {
			"\t\tpop ebx\n" + 
			"\t\tpop eax\n" + 
			String.Format("\t\t{0} eax, ebx\n", oper) + 
			"\t\tpush eax\n";
		}
		
		CompileInst(nilInst : NIL.Inst, inst : NIT, args : long) : string {
			match(inst) {
				| null => ""
				
				| Checked(_, inst) =>
					CompileInst(nilInst, inst, args);
				
				| Add => CompileMath("add")
				| Sub => CompileMath("sub")
				| Mul => CompileMath("imul")
				| Div => CompileMath("idiv")
				
				| And => CompileMath("and")
				| Or  => CompileMath("or")
				| Xor => CompileMath("xor")
				
				| Call(target is MethodInfo) =>
					String.Format("\t\tcall method_{0}\n", target.Name) + 
					{
						if(target.ReturnType.IsAssignableFrom(typeof(void)))
							""
						else
							"\t\tpush eax\n"
					}
				
				| Push(value) =>
					match(value) {
						| value is int =>
							String.Format("\t\tpush {0}\n", value)
						| x =>
							throw Exception(String.Format("Push unknown {0} {1}", x.GetType(), x))
					}
				
				| Dup =>
					"\t\tpop eax\n" + 
					"\t\tpush eax\n" + 
					"\t\tpush eax\n"
				
				| LoadArg(arg) =>
					String.Format("\t\tmov eax, [ebp+{0}]\n", (args - arg) * 4 + 4) + 
					"\t\tpush eax\n"
				
				| StoreArg(arg) =>
					"\t\tpop eax\n" + 
					String.Format("\t\tmov [ebp+{0}], eax\n", (args - arg) * 4 + 4)
				
				| LoadLocal(local) =>
					String.Format("\t\tmov eax, [ebp-{0}]\n", local * 4 + 4) + 
					"\t\tpush eax\n"
				
				| StoreLocal(local) =>
					"\t\tpop eax\n" + 
					String.Format("\t\tmov [ebp-{0}], eax\n", local * 4 + 4)
				
				| LoadField(field, _) =>
					String.Format("\t\tmov eax, [field_{0}]\n", field) + 
					"\t\tpush eax\n"
				
				| StoreField(field, _) =>
					"\t\tpop eax\n" + 
					String.Format("\t\tmov [field_{0}], eax\n", field)
				
				| LoadIndirect(t) =>
					"\t\tpop eax\n" + 
					"\t\txor ebx, ebx\n" + 
					(match(t) {
						| _ is byte =>
							"\t\tmov bl, [eax]\n"
						| x =>
							throw Exception(String.Format("LoadIndirect {0}", x.GetType()))
					}) + 
					"\t\tpush ebx\n"
				
				| StoreIndirect(t) =>
					"\t\tpop eax\n" + 
					"\t\tpop ebx\n" + 
					match(t) {
						| _ is byte =>
							"\t\tmov [ebx], al\n"
						| x =>
							throw Exception(String.Format("StoreIndirect {0}", x.GetType()))
					}
				
				| CondBranch(taken, _, comp, _) =>
					def next = match(nilInst) {
						| Inst(pos, size, _, _, _, _) =>
							pos + size
					};
					
					if(comp == null) {
						String.Format(
							"\t\tjmp .block_{0:X}\n",
							next + taken
						)
					} else
						match(comp) {
							| True | False =>
								"\t\tpop eax\n" + 
								"\t\ttest eax, eax\n" + 
								String.Format(
									"\t\t{0} .block_{1:X}\n",
									match(comp) {
										| True => "jnz"
										| False => "jz"
										| _ => throw Exception("Should never hit this...")
									},
									next + taken
								)
							| _ =>
								throw Exception("Comparing branches not supported")
						}
				
				| Compare(comp, _) =>
					def asm = "\t\tpop ebx\n" + 
					"\t\tpop eax\n" + 
					"\t\txor ecx, ecx\n" + 
					"\t\tcmp eax, ebx\n" + 
					match(comp) {
						| EQ =>
							"\t\tsete cl\n"
						| LT =>
							"\t\tsetl cl\n"
						| _ => throw Exception("Unknown comparison")
					};
					asm + 
					"\t\tpush ecx\n"
				
				| Ret() =>
					"\t\tjmp .end\n"
				
				| Convert(_) =>
					""
				
				| x => throw Exception(String.Format("{0}", x))
			}
		}
	}
}
